﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ARCam3D.Tuple;

namespace ARCam3D.Imaging.Processing
{
    public class ComponentsDetector
    {
        public List<List<Point2I>> DetectComponents(BinaryImage image)
        {

            int[,] blobsImage = new int[image.Width, image.Height];
            Dictionary<int, List<int>> blobsGroupsToBlobs = new Dictionary<int, List<int>>();
            List<int> blobsToBlobGroups = new List<int>();
            Dictionary<int, List<Point2I>> blobsPoints = new Dictionary<int, List<Point2I>>();

            for (int y = 0; y < image.Height; ++y)
            {
                for (int x = 0; x < image.Width; ++x)
                {
                    if (image.GetValue(x, y))
                    {
                        bool blobAssigned = false;
                        if (x > 0 && image.GetValue(x - 1, y))
                        {
                            //assigning blob to pixel
                            blobsImage[x, y] = blobsImage[x - 1, y];
                            blobsPoints[blobsImage[x, y]].Add(new Point2I(x, y));
                            blobAssigned = true;
                        }
                        
                        if (y > 0)
                        {
                            for (int i = -1; i <= 1; ++i)
                            {
                                if (x + i > 0 && x + i < image.Width && image.GetValue(x + i, y - 1))
                                {
                                    if (blobAssigned)
                                    {
                                        //linking blobs to same group
                                        int blobGroup = blobsToBlobGroups[blobsImage[x, y]];
                                        int oldBlobGroup = blobsToBlobGroups[blobsImage[x + i, y - 1]];

                                        if (blobGroup != oldBlobGroup)
                                        {
                                            foreach (int blob in blobsGroupsToBlobs[oldBlobGroup])
                                            {
                                                blobsToBlobGroups[blob] = blobGroup;
                                            }
                                            blobsGroupsToBlobs[blobGroup].AddRange(blobsGroupsToBlobs[oldBlobGroup]);
                                            blobsGroupsToBlobs.Remove(oldBlobGroup);
                                        }
                                    }
                                    else
                                    {
                                        //assigning blob to pixel
                                        blobsImage[x, y] = blobsImage[x + i, y - 1];
                                        blobsPoints[blobsImage[x, y]].Add(new Point2I(x, y));
                                        blobAssigned = true;
                                    }
                                }
                            }
                            
                        }

                        if (!blobAssigned)
                        {
                            //creating new blob
                            int blobIndex = blobsToBlobGroups.Count;
                            blobsImage[x, y] = blobIndex;

                            blobsPoints.Add(blobIndex, new List<Point2I>());
                            blobsPoints[blobIndex].Add(new Point2I(x, y));

                            blobsGroupsToBlobs.Add(blobIndex, new List<int>());
                            blobsGroupsToBlobs[blobIndex].Add(blobIndex);

                            blobsToBlobGroups.Add(blobIndex);
                        }
                    }
                }
            }

            //merging blobs with same groups into components
            List<List<Point2I>> components = new List<List<Point2I>>();

            foreach (List<int> blobGroup in blobsGroupsToBlobs.Values)
            {
                List<Point2I> points = new List<Point2I>();
                foreach (int blob in blobGroup)
                {
                    points.AddRange(blobsPoints[blob]);
                }
                if (points.Count >= 4)
                {
                    components.Add(points);
                }
            }

            return components;
        }
    }
}
